package cn.yemuysy.code.check.service.impl;

import cn.yemuysy.code.check.model.InfringingWord;
import cn.yemuysy.code.check.model.InfringingWordMatch;
import cn.yemuysy.code.check.service.InfringingWordService;
import cn.yemuysy.code.check.util.TrieVisualizationUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 使用Aho-Corasick算法实现的侵权词服务，用于高效的多模式字符串匹配。
 * 此实现是线程安全的，并支持动态添加词汇。
 * <p>
 * 内部通过 {@link TrieManager} 类来管理不同类型的Trie树（区分/不区分大小写，主/增量），
 * 从而将Trie树的状态管理与服务的主业务逻辑分离。
 * </p>
 */
public class TrieInfringingWordService implements InfringingWordService {

    /**
     * 用于记录日志的Logger实例
     */
    private static final Logger log = LoggerFactory.getLogger(TrieInfringingWordService.class);

    /**
     * 默认的替换字符串，用于替换检测到的侵权词
     */
    private static final String DEFAULT_REPLACEMENT = "***";

    /**
     * 默认的增量同步到全量的时间间隔（分钟）
     */
    private static final long DEFAULT_SYNC_INTERVAL_MINUTES = 30;

    /**
     * 用于保证线程安全的读写锁
     */
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 用于定时执行增量同步到全量的调度器
     */
    private ScheduledExecutorService scheduler;

    /**
     * 增量同步到全量的时间间隔（分钟）
     */
    private long syncIntervalMinutes = DEFAULT_SYNC_INTERVAL_MINUTES;

    /**
     * 管理区分大小写的主Trie树
     */
    private final TrieManager mainCaseSensitiveManager;

    /**
     * 管理不区分大小写的主Trie树
     */
    private final TrieManager mainCaseInsensitiveManager;

    /**
     * 管理区分大小写的增量Trie树
     */
    private final TrieManager deltaCaseSensitiveManager;

    /**
     * 管理不区分大小写的增量Trie树
     */
    private final TrieManager deltaCaseInsensitiveManager;

    /**
     * 包含所有TrieManager的列表，方便统一操作
     */
    private final List<TrieManager> allManagers;

    /**
     * 仅包含增量TrieManager的列表
     */
    private final List<TrieManager> deltaManagers;

    /**
     * 构造一个新的TrieInfringingWordService实例。
     * 初始化四个不同类型的Trie树管理器（区分/不区分大小写，主/增量），
     * 并创建用于统一操作的管理器列表。
     * 默认不启动自动同步，需要调用startAutoSync()方法启动。
     */
    public TrieInfringingWordService() {
        log.info("TrieInfringingWordService 初始化...");
        this.mainCaseSensitiveManager = new TrieManager(true, lock);
        this.mainCaseInsensitiveManager = new TrieManager(false, lock);
        this.deltaCaseSensitiveManager = new TrieManager(true, lock);
        this.deltaCaseInsensitiveManager = new TrieManager(false, lock);

        this.allManagers = Collections.unmodifiableList(Arrays.asList(
            mainCaseSensitiveManager, mainCaseInsensitiveManager,
            deltaCaseSensitiveManager, deltaCaseInsensitiveManager
        ));
        this.deltaManagers = Collections.unmodifiableList(Arrays.asList(
            deltaCaseSensitiveManager, deltaCaseInsensitiveManager
        ));
        log.info("TrieInfringingWordService 已初始化。");
    }

    /**
     * 启动自动同步增量到全量的定时任务。
     * 如果已经启动，则先停止现有任务，再重新启动。
     * 
     * @return 当前实例，用于链式调用
     */
    public TrieInfringingWordService startAutoSync() {
        stopAutoSync();

        log.info("启动自动同步增量到全量的定时任务，间隔：{} 分钟", syncIntervalMinutes);
        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread thread = new Thread(r, "trie-auto-sync");
            thread.setDaemon(true);
            return thread;
        });

        scheduler.scheduleAtFixedRate(() -> {
            try {
                log.info("执行定时增量同步到全量任务");
                mergeAndRebuild();
            } catch (Exception e) {
                log.error("定时增量同步到全量任务执行失败", e);
            }
        }, syncIntervalMinutes, syncIntervalMinutes, TimeUnit.MINUTES);

        return this;
    }

    /**
     * 停止自动同步增量到全量的定时任务。
     * 如果未启动，则不执行任何操作。
     * 
     * @return 当前实例，用于链式调用
     */
    public TrieInfringingWordService stopAutoSync() {
        if (scheduler != null && !scheduler.isShutdown()) {
            log.info("停止自动同步增量到全量的定时任务");
            scheduler.shutdown();
            try {
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    scheduler.shutdownNow();
                }
            } catch (InterruptedException e) {
                scheduler.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
        return this;
    }

    /**
     * 设置自动同步增量到全量的时间间隔。
     * 如果自动同步已启动，则会重新启动以应用新的间隔。
     * 
     * @param minutes 时间间隔（分钟），必须大于0
     * @return 当前实例，用于链式调用
     * @throws IllegalArgumentException 如果minutes小于等于0
     */
    public TrieInfringingWordService setSyncInterval(long minutes) {
        if (minutes <= 0) {
            throw new IllegalArgumentException("同步间隔必须大于0分钟");
        }

        boolean wasRunning = scheduler != null && !scheduler.isShutdown();
        this.syncIntervalMinutes = minutes;
        log.info("设置自动同步增量到全量的时间间隔为：{} 分钟", minutes);

        // 如果之前在运行，则重新启动以应用新的间隔
        if (wasRunning) {
            startAutoSync();
        }

        return this;
    }

    /**
     * 获取当前设置的自动同步增量到全量的时间间隔。
     * 
     * @return 时间间隔（分钟）
     */
    public long getSyncInterval() {
        return syncIntervalMinutes;
    }

    /**
     * 初始化并加载全量侵权词到主Trie树中。
     * 此方法应在服务启动时调用。
     *
     * @param infringingWords 需要加载的侵权词列表
     */
    @Override
    public void initializeWithWords(List<InfringingWord> infringingWords) {
        if (infringingWords == null || infringingWords.isEmpty()) {
            log.warn("初始化侵权词列表为空");
            return;
        }

        lock.writeLock().lock();
        try {
            log.info("开始全量加载 {} 个侵权词...", infringingWords.size());

            // 清空现有的主Trie树
            mainCaseSensitiveManager.clear();
            mainCaseInsensitiveManager.clear();

            // 将词汇添加到相应的主Trie树中
            for (InfringingWord word : infringingWords) {
                if (word == null || word.getWord() == null || word.getWord().isEmpty()) {
                    continue;
                }

                if (word.isCaseSensitive()) {
                    mainCaseSensitiveManager.addWord(word);
                } else {
                    mainCaseInsensitiveManager.addWord(word);
                }
            }

            // 重建失败链接
            mainCaseSensitiveManager.ensureFailureLinksBuilt();
            mainCaseInsensitiveManager.ensureFailureLinksBuilt();

            log.info("全量侵权词加载完成，共加载 {} 个词", infringingWords.size());
        } finally {
            lock.writeLock().unlock();
        }
    }


    /**
     * 向服务中添加一个侵权词。
     * 根据侵权词的caseSensitive属性，将其添加到相应的增量Trie树中。
     * 此方法是线程安全的，使用写锁来保护对增量Trie的修改。
     *
     * @param infringingWord 要添加的侵权词
     */
    @Override
    public void addInfringingWord(InfringingWord infringingWord) {
        if (infringingWord == null || infringingWord.getWord() == null || infringingWord.getWord().isEmpty()) {
            return;
        }

        lock.writeLock().lock();
        try {
            if (infringingWord.isCaseSensitive()) {
                deltaCaseSensitiveManager.addWord(infringingWord);
            } else {
                deltaCaseInsensitiveManager.addWord(infringingWord);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 向服务中批量添加多个侵权词。
     * 此方法比单独调用多次addInfringingWord更高效，因为它只获取一次写锁。
     * 根据每个侵权词的caseSensitive属性，将其添加到相应的增量Trie树中。
     *
     * @param infringingWords 要添加的侵权词列表
     */
    @Override
    public void addInfringingWords(List<InfringingWord> infringingWords) {
        if (infringingWords == null || infringingWords.isEmpty()) {
            return;
        }

        lock.writeLock().lock();
        try {
            log.info("批量添加 {} 个侵权词。", infringingWords.size());
            for (InfringingWord word : infringingWords) {
                if (word == null || word.getWord() == null || word.getWord().isEmpty()) {
                    continue;
                }
                if (word.isCaseSensitive()) {
                    deltaCaseSensitiveManager.addWord(word);
                } else {
                    deltaCaseInsensitiveManager.addWord(word);
                }
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 将增量Trie中的词汇合并到主Trie中，并重建主Trie的失败链接。
     * 此方法执行以下操作：
     * <ol>
     *   <li>将区分大小写的增量Trie合并到对应的主Trie中</li>
     *   <li>将不区分大小写的增量Trie合并到对应的主Trie中</li>
     *   <li>清空两个增量Trie</li>
     *   <li>重建两个主Trie的失败链接</li>
     * </ol>
     * 这是一个非常耗时的操作，应该在系统负载较低时调用。
     * 合并操作使用写锁保护，会阻塞所有读写操作，直到合并完成。
     */
    public void mergeAndRebuild() {
        log.info("开始合并增量Trie到主Trie并重建...");
        lock.writeLock().lock();
        try {
            mainCaseSensitiveManager.mergeFrom(deltaCaseSensitiveManager);
            mainCaseInsensitiveManager.mergeFrom(deltaCaseInsensitiveManager);

            deltaCaseSensitiveManager.clear();
            deltaCaseInsensitiveManager.clear();

            mainCaseSensitiveManager.ensureFailureLinksBuilt();
            mainCaseInsensitiveManager.ensureFailureLinksBuilt();
            log.info("增量Trie合并与重建完成。");
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 检查文本中是否包含任何侵权词。
     * 此方法会在所有四种Trie树（区分/不区分大小写，主/增量）中查找匹配项，
     * 并对结果进行排序和过滤，以处理重叠的匹配。
     * 在执行读操作前，会确保所有增量Trie树的失败链接已构建。
     *
     * @param text 要检查的文本
     * @param site 检查的站点上下文（可以为null表示所有站点）
     * @return 在文本中找到的匹配列表
     */
    @Override
    public List<InfringingWordMatch> checkText(String text, String site) {
        if (text == null || text.isEmpty()) {
            return Collections.emptyList();
        }

        deltaManagers.forEach(TrieManager::ensureFailureLinksBuilt);

        lock.readLock().lock();
        try {
            List<InfringingWordMatch> allMatches = new ArrayList<>();
            for (TrieManager manager : allManagers) {
                allMatches.addAll(manager.findMatches(text, site));
            }

            if (allMatches.isEmpty()) {
                return Collections.emptyList();
            }

            allMatches.sort(Comparator.comparingInt(InfringingWordMatch::getPosition));
            return filterOverlappingMatches(allMatches);
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * 过滤重叠的匹配项，遵循"最长匹配"原则。
     * 例如，如果文本是 "abcdefg"，匹配到 "abc" 和 "abcdef"，则只保留 "abcdef"。
     *
     * @param matches 已按位置排序的匹配结果列表
     * @return 过滤重叠后的匹配结果列表
     */
    private List<InfringingWordMatch> filterOverlappingMatches(List<InfringingWordMatch> matches) {
        // 如果只有0或1个匹配项，无需过滤
        if (matches.size() <= 1) {
            return matches;
        }

        // 用于存储过滤后的结果
        List<InfringingWordMatch> filtered = new ArrayList<>();
        // 记录上一个处理的匹配项，初始为null
        InfringingWordMatch lastMatch = null;

        // 遍历所有匹配项（已按位置排序）
        for (InfringingWordMatch currentMatch : matches) {
            // 处理第一个匹配项的特殊情况
            if (lastMatch == null) {
                lastMatch = currentMatch;
                continue;
            }

            // 计算上一个匹配的结束位置（位置 + 长度）
            int lastMatchEndPosition = lastMatch.getPosition() + lastMatch.getOriginalText().length();
            // 当前匹配的起始位置
            int currentMatchPosition = currentMatch.getPosition();

            // 检查是否存在重叠
            // 只有当当前匹配的起始位置严格小于上一个匹配的结束位置时，才认为存在重叠
            // 如果起始位置等于结束位置，则认为是相邻而非重叠（例如："abc"和"cde"）
            if (currentMatchPosition < lastMatchEndPosition) {
                // 存在重叠，应用"最长匹配"原则
                // 如果当前匹配更长，则替换上一个匹配
                if (currentMatch.getOriginalText().length() > lastMatch.getOriginalText().length()) {
                    // 例如：上一个是"abc"，当前是"abcde"，保留"abcde"
                    lastMatch = currentMatch;
                }
                // 否则，忽略当前匹配（因为它是较短的重叠匹配）
                // 例如：上一个是"abcde"，当前是"abc"，保留"abcde"
            } else {
                // 没有重叠，将上一个匹配添加到结果列表
                filtered.add(lastMatch);
                // 更新lastMatch为当前匹配，准备下一轮比较
                lastMatch = currentMatch;
            }
        }

        // 将最后一个处理的匹配项添加到结果中
        // 这是必要的，因为循环中只有在处理下一个非重叠匹配时才会添加上一个匹配
        if (lastMatch != null) {
            filtered.add(lastMatch);
        }

        return filtered;
    }

    /**
     * 使用默认替换（***）从文本中删除所有侵权词。
     * 此方法是{@link #removeInfringingWords(String, String, String)}的便捷重载。
     *
     * @param text 要清理的文本
     * @param site 检查的站点上下文（可以为null表示所有站点）
     * @return 已将侵权词替换为***的清理后文本
     */
    @Override
    public String removeInfringingWords(String text, String site) {
        return removeInfringingWords(text, site, DEFAULT_REPLACEMENT);
    }

    /**
     * 从文本中删除所有侵权词，使用指定的替换字符串。
     * 此方法首先使用{@link #checkText(String, String)}查找所有匹配项，
     * 然后使用{@link #replaceMatches(String, List, String)}替换这些匹配项。
     *
     * @param text 要清理的文本
     * @param site 检查的站点上下文（可以为null表示所有站点）
     * @param replacement 用于替换侵权词的字符串（如果为null，将使用默认值"***"）
     * @return 已替换侵权词的清理后文本
     */
    @Override
    public String removeInfringingWords(String text, String site, String replacement) {
        if (text == null || text.isEmpty()) {
            return text;
        }
        String finalReplacement = (replacement == null) ? DEFAULT_REPLACEMENT : replacement;

        List<InfringingWordMatch> matches = checkText(text, site);
        if (matches.isEmpty()) {
            return text;
        }

        return replaceMatches(text, matches, finalReplacement);
    }

    /**
     * 根据匹配结果，用指定的替换字符串替换文本中的侵权词。
     *
     * @param text        原始文本
     * @param matches     侵权词匹配结果
     * @param replacement 用于替换的字符串
     * @return 替换后的文本
     */
    public String replaceMatches(String text, List<InfringingWordMatch> matches, String replacement) {
        StringBuilder sb = new StringBuilder(text);

        // 从后往前替换，以避免位置索引问题
        // 这是因为替换操作会改变字符串的长度，从而影响后续替换的位置
        // 例如：如果从前往后替换，替换第一个匹配后，后面所有匹配的位置都会变得不准确
        // 而从后往前替换，已处理的匹配位于当前匹配之后，其位置不会受到影响
        for (int i = matches.size() - 1; i >= 0; i--) {
            InfringingWordMatch match = matches.get(i);
            sb.replace(match.getPosition(), match.getPosition() + match.getOriginalText().length(), replacement);
        }

        return sb.toString();
    }

    /**
     * 导出指定Trie树的DOT格式表示，用于可视化。
     * 此方法使用 {@link TrieVisualizationUtil} 工具类来生成DOT格式。
     *
     * @param caseSensitive 指定要导出的Trie是区分大小写 (true) 还是不区分大小写 (false)
     * @param isDelta       指定要导出的Trie是增量Trie (true) 还是主Trie (false)
     * @return DOT格式的字符串
     */
    public String exportTrieAsDot(boolean caseSensitive, boolean isDelta) {
        TrieNode rootToExport;
        if (isDelta) {
            rootToExport = caseSensitive ? deltaCaseSensitiveManager.getRoot() : deltaCaseInsensitiveManager.getRoot();
        } else {
            rootToExport = caseSensitive ? mainCaseSensitiveManager.getRoot() : mainCaseInsensitiveManager.getRoot();
        }

        return TrieVisualizationUtil.exportTrieAsDot(rootToExport, caseSensitive, isDelta);
    }

    /**
     * 关闭服务，释放资源。
     * 此方法会停止自动同步任务并清理相关资源。
     * 在应用程序关闭时应调用此方法以防止资源泄漏。
     */
    public void close() {
        log.info("关闭TrieInfringingWordService，释放资源");
        stopAutoSync();
    }
}
